package cn.ante.ymcc.service.impl;

import cn.ante.ymcc.constant.Constance;
import cn.ante.ymcc.domain.Course;
import cn.ante.ymcc.domain.CourseMarket;
import cn.ante.ymcc.domain.CourseOrder;
import cn.ante.ymcc.domain.CourseOrderItem;
import cn.ante.ymcc.dto.*;
import cn.ante.ymcc.exception.GlobalException;
import cn.ante.ymcc.feign.CourseFeignClient;
import cn.ante.ymcc.mapper.CourseOrderMapper;
import cn.ante.ymcc.result.JSONResult;
import cn.ante.ymcc.service.ICourseOrderItemService;
import cn.ante.ymcc.service.ICourseOrderService;
import cn.ante.ymcc.util.AssertUtil;
import cn.ante.ymcc.util.CodeGenerateUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import org.apache.commons.lang.StringUtils;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.aspectj.weaver.ast.Var;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author Nai
 * @since 2022-08-19
 */
@Service
public class CourseOrderServiceImpl extends ServiceImpl<CourseOrderMapper, CourseOrder> implements ICourseOrderService {

    private static final String TOKEN_KEY = "token:%s:%s";

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Autowired
    private CourseFeignClient courseFeignClient;

    @Autowired
    private ICourseOrderService courseOrderService;

    @Autowired
    private ICourseOrderItemService courseOrderItemService;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Override
    @Transactional
    public String placeOrder(CourseOrderInfoParamDto dto) {
        // 校验课程不能为空
        boolean isNUll = dto.getCourseIds().size() > 0;
        AssertUtil.isNotNull(isNUll,"参数为空");
        // jar303对参数进行校验，再校验token是否存在
        Long loginId = 3L;

        // 生成token时存入的时一个字符串courseid
        String courseIdsStr = StringUtils.join(dto.getCourseIds(), ",");

        String key = String.format(TOKEN_KEY,loginId,courseIdsStr);

        // 去redis中查找token
        Object token = redisTemplate.opsForValue().get(key);

        // 断言该token不为空，防重
        AssertUtil.isNotNull(token,"请勿重复下单");

        // 判断redis中token是否与前端传过来的一致
        boolean equals = dto.getToken().equals(token.toString());

        AssertUtil.isTrue(equals,"token错误");

        // token相等，进入下单逻辑
//        List<Long> courseIds = courseOrderInfoDto.getCourseIds();

        // 根据id查询到课程信息，将结算页的课程信息接口包装成feign,然后调用
        JSONResult jsonResult = courseFeignClient.courseOrderInfo(courseIdsStr);
        AssertUtil.isTrue(JSONResult.success().isSuccess(),JSONResult.error().getMessage());
        // 断言课程是否存在
        AssertUtil.isNotNull(jsonResult.getData(),"课程不存在");
        // 将object的jason格式转成自己想要的类
        Object resultData = jsonResult.getData();
        Course2OrderInfoVo course2OrderInfoVo = JSONObject.parseObject(JSON.toJSONString(resultData), Course2OrderInfoVo.class);
        // 拿到课程的信息和课程销售信息
        List<CourseDto> courseInfos = course2OrderInfoVo.getCourseInfos();
        // 拿到多个课程的金额
        BigDecimal totalAmount = course2OrderInfoVo.getTotalAmount();
        // 设置主订单信息
        CourseOrder courseOrder = new CourseOrder();
        courseOrder.setOrderNo(CodeGenerateUtils.generateOrderSn(loginId));
        Date now = new Date();
        courseOrder.setCreateTime(now);
        courseOrder.setStatusOrder(0); // 0待支付 1支付成功 2手动取消订单 未支付 3支付失败  4超时自动取消
        courseOrder.setPayType(dto.getPayType());
        courseOrder.setTotalAmount(totalAmount);
        courseOrder.setUserId(loginId);

        // 保存主订单
//        courseOrderService.insert(courseOrder); 使用mq开启事务消息，保证保存订单和发送消息的一致性，应该将该本地事务交给监听器完成

        // 拼接主订单的标题
        StringBuffer sb = new StringBuffer();
        sb.append("购买课程【");
        // 保存子订单明细
        courseInfos.stream().forEach(courseDto -> {
            CourseOrderItem courseOrderItem = new CourseOrderItem();
            Course course = courseDto.getCourse();
            CourseMarket courseMarket = courseDto.getCourseMarket();
            boolean b = course.getStatus() == Constance.Course2OrderState.COURSE_ONLINE;
            AssertUtil.isTrue(b,"非法请求");
            courseOrderItem.setCourseId(course.getId());
            courseOrderItem.setOrderNo(courseOrder.getOrderNo());
            // mq事务消息是在监听器中执行本地事务，所以此时没有主订单id
//            courseOrderItem.setOrderId(courseOrder.getId());
            courseOrderItem.setCourseName(course.getName());
            courseOrderItem.setCoursePic(course.getPic());
            courseOrderItem.setAmount(courseMarket.getPrice());
            courseOrderItem.setCount(1);
            courseOrderItem.setCreateTime(now);
            // 将每一条子订单保存
//            courseOrderItemService.insert(courseOrderItem);
            // @TODO 这里的子订单总计有问题
            courseOrder.setTotalCount(courseOrderItem.getCount());
            sb.append(courseOrderItem.getCourseName()).append("-");

            // 当mq事务发送消息时，在courseorder的类中加入明细集合，然后将主订单对象传给监听器中当扩展参数使用，执行本地事务
            courseOrder.getItems().add(courseOrderItem);
        });
        sb.append("】").append("支付了：").append(courseOrder.getTotalAmount()).append("元");
        courseOrder.setTitle(sb.toString());
//        updateById(courseOrder);
        // 扩展参数设置
        HashMap<String, Object> exParamsMap = new HashMap<>();
        exParamsMap.put("loginId",loginId);
        exParamsMap.put("courseId",courseIdsStr);
        // 发送保存支付单的事务消息，注入mq，传递到监听器中
//        String txProducerGroup,// 同监听器组名一致，到时候事务消息会找到监听器，执行监听器里面的方法
//        String destination, // 拼接一个目的地：就是topic和tag
//        org.springframework.messaging.Message<?> message, 内容应该是事务消息，保存支付单，那就是支付单需要的数据内容
//        Object arg  用来传递给事务监听器 执行本地事务的数据
        Order2PayDto order2PayDto = new Order2PayDto(
                courseOrder.getTotalAmount(),
                courseOrder.getPayType(),
                courseOrder.getOrderNo(),
                courseOrder.getUserId(),
                JSON.toJSONString(exParamsMap),
                courseOrder.getTitle()
        );
        Message<String> message = MessageBuilder.withPayload(JSON.toJSONString(order2PayDto)).build();
        TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(
                "txorder2pay",
                "topic-order:tags-payorder",
                message,
                courseOrder);
        // 执行事务是否是已经提交的状态
        // 检查本地事务执行的状态是否是成功的
        if (!(sendResult.getLocalTransactionState() == LocalTransactionState.COMMIT_MESSAGE) || !(sendResult.getSendStatus() == SendStatus.SEND_OK)){
            throw  new GlobalException("请勿重复下单","4000");
        }

        //5.删除防重复token
        redisTemplate.delete(key);

        try {
            // 6.发送延迟消息给mq,当订单支付超时，就取消订单
            Message<String> delayMessage = MessageBuilder.withPayload(courseOrder.getOrderNo()).build();
            SendResult sendRe = rocketMQTemplate.syncSend("cancel-order-topic:paytimeout-tags",
                    delayMessage,
                    3000,
                    4);
            SendStatus sendStatus = sendRe.getSendStatus();
            if (sendStatus != SendStatus.SEND_OK){
                // 这里应该是兜底数据
                //发送消息失败  兜底
                //1.重试3次
                //2.讲消息体保存到数据库（提供管理界面，手动出发发布消息），发钉钉，短信，邮件通知运维人工介入处理
            }
        } catch (Exception e) {
            e.printStackTrace();
            //发送消息失败  兜底
            //1.重试3次
            //2.将消息体保存到数据库（提供管理界面，手动出发发布消息），发钉钉，短信，邮件通知运维人工介入处理
        }


        // 返回订单号
        return courseOrder.getOrderNo();
    }

    /**
    * @Description: mq事务中执行事务的逻辑代码
    * @Author: Nai
    * @Version: 1.0
    * @Date:  2022/8/20 20:06
    */
    @Override
    public void saveOrderAndIterm(CourseOrder courseOrder) {
        // 执行本地事务的业务方法
        CourseOrder order = selectOrderOne(courseOrder.getOrderNo());

        // 断言根据订单号查询到的主订单是否存在，存在就抛重复提交异常
        AssertUtil.isNull(order,"请勿重复下单");

        // 不存在就保存该主订单消息
        courseOrderService.insert(courseOrder);

        List<CourseOrderItem> items = courseOrder.getItems();
        // 遍历主订单消息中的明细菜单，设置明细订单中的主订单id,然后保存明细订单
        for (CourseOrderItem item:items) {
            item.setOrderId(courseOrder.getId());
            courseOrderItemService.insert(item);
        }
    }

    /**
    * @Description: 根据订单号查询订单表
    * @Author: Nai
    * @Version: 1.0
    * @Date:  2022/8/20 20:06
    */
    @Override
    public CourseOrder selectOrderOne(String courseOrderNo) {
        Wrapper<CourseOrder> wrapper = new EntityWrapper<>();
        wrapper.eq("order_no",courseOrderNo);
        return courseOrderService.selectOne(wrapper);
    }

    @Override
    public void updateOrderByPayNotify(PayAliNotifyDto payAliNotifyDto) {
        String orderNo = payAliNotifyDto.getOrderNo();
        // 业务幂等性处理
        CourseOrder courseOrder = courseOrderService.selectOrderOne(orderNo);
        if (courseOrder != null && courseOrder.getStatusOrder() == 0){
            courseOrder.setStatusOrder(1);
            courseOrder.setUpdateTime(new Date());
            updateById(courseOrder);
        }
        // 还有其他的业务逻辑处理
        //  === 有些业务bug，没办法系统直接处理，那么就需要由人工介入
        //你支付成功了，但是订单的状态不对了
        //还没有支付成功，就被手动取消了  应该执行退款逻辑
        //1.记录支付结果处理异常日志
        //2.直接给负责人钉钉，手机，邮箱，发异常处理消息，由人工介入处理

    }

    /**
    * @Description: 订单超时取消逻辑，判断订单是否存在和订单的状态是否是带支付状态
    * @Author: Nai
    * @Version: 1.0
    * @Date:  2022/8/22 19:01
    */
    @Override
    public void cancelOrderByPayTimeOut(String courseOrderNo) {
        CourseOrder courseOrder = selectOrderOne(courseOrderNo);
        // 当订单为空的时候，不做任何处理
        if (courseOrder == null){
            return;
        }
        // 当订单不是未支付状态的时候，直接返回，不做任何的修改
        if (courseOrder.getStatusOrder() != 0){
            return; // 如果支付了就走退款逻辑
        }
        courseOrder.setStatusOrder(4); // 超时自动取消订单
        courseOrder.setUpdateTime(new Date());
        updateById(courseOrder);

    }

    @Override
    @Transactional
    public String killPlaceOrder(CourseOrderInfoParamDto dto) {
        // 订单号非空
        String orderNo = dto.getOrderNo();
        AssertUtil.isNotNull(orderNo,"订单号不存在");

        // 从redis中拿到预创订单
        PreCourseOrder preOrder = (PreCourseOrder)redisTemplate.opsForValue().get(orderNo);

        // 拼接存入token中的key
        Long loginId = 3L;
        String key = String.format(TOKEN_KEY,loginId,preOrder.getCourseId());
        Object token = redisTemplate.opsForValue().get(key);

        // 判断token
        AssertUtil.isNotNull(token,"请勿重复下单");
        boolean equals = dto.getToken().equals(token.toString());
        AssertUtil.isTrue(equals,"token错误");

        // 创建订单  和明细订单
        CourseOrder courseOrder = new CourseOrder();
        courseOrder.setTotalCount(preOrder.getTotalCount());
        courseOrder.setUserId(preOrder.getUserId());
        courseOrder.setOrderNo(preOrder.getOrderNo());
        courseOrder.setTotalAmount(preOrder.getTotalAmount());
        courseOrder.setPayType(dto.getPayType());
        courseOrder.setStatusOrder(0); // 0待支付 1支付成功 2手动取消订单 未支付 3支付失败  4超时自动取消
        courseOrder.setCreateTime(new Date());

        // 拼接主订单的标题
        StringBuffer sb = new StringBuffer();
        sb.append("秒杀课程【").append(preOrder.getCourseName())
                .append("】").append("支付了：").append(preOrder.getTotalAmount()).append("元");
        courseOrder.setTitle(sb.toString());
//        courseOrderService.insert(courseOrder);
        CourseOrderItem courseOrderItem = new CourseOrderItem();
//        courseOrderItem.setOrderId(courseOrder.getId());
        courseOrderItem.setOrderNo(preOrder.getOrderNo());
        courseOrderItem.setCourseName(preOrder.getCourseName());
        courseOrderItem.setCoursePic(preOrder.getCoursePic());
        courseOrderItem.setCount(1);
        courseOrderItem.setAmount(preOrder.getTotalAmount());
        courseOrderItem.setCreateTime(new Date());
        courseOrderItem.setCourseId(preOrder.getCourseId());
//        courseOrderItemService.insert(courseOrderItem);
        courseOrder.getItems().add(courseOrderItem);

        // 发送事务消息到mq,消息传递为订单号，本地事务参数为courseorder里面封装了明细订单
        // 扩展参数设置
        HashMap<String, Object> exParamsMap = new HashMap<>();
        exParamsMap.put("loginId",loginId);
        exParamsMap.put("courseId",preOrder.getCourseId());
        Order2PayDto order2PayDto = new Order2PayDto(
                preOrder.getTotalAmount(),
                dto.getPayType(),
                preOrder.getOrderNo(),
                preOrder.getUserId(),
                JSON.toJSONString(exParamsMap),
                courseOrder.getTitle()
        );
        Message<String> message = MessageBuilder.withPayload(JSON.toJSONString(order2PayDto)).build();
        TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(
                "txorder2pay",
                "topic-order:tags-payorder",
                message,
                courseOrder);

        // 执行事务是否是已经提交的状态
        // 检查本地事务执行的状态是否是成功的
        if (!(sendResult.getLocalTransactionState() == LocalTransactionState.COMMIT_MESSAGE) || !(sendResult.getSendStatus() == SendStatus.SEND_OK)){
            throw  new GlobalException("请勿重复下单","4000");
        }

        //5.删除防重复token
        redisTemplate.delete(key);

        // 删除预创订单
        redisTemplate.delete(orderNo);

        try {
            // 6.发送延迟消息给mq,当订单支付超时，就取消订单
            Message<String> delayMessage = MessageBuilder.withPayload(courseOrder.getOrderNo()).build();
            SendResult sendRe = rocketMQTemplate.syncSend("cancel-order-topic:paytimeout-tags",
                    delayMessage,
                    3000,
                    5);
            SendStatus sendStatus = sendRe.getSendStatus();
            if (sendStatus != SendStatus.SEND_OK){
                // 这里应该是兜底数据
                //发送消息失败  兜底
                //1.重试3次
                //2.讲消息体保存到数据库（提供管理界面，手动出发发布消息），发钉钉，短信，邮件通知运维人工介入处理
            }
        } catch (Exception e) {
            e.printStackTrace();
            //发送消息失败  兜底
            //1.重试3次
            //2.将消息体保存到数据库（提供管理界面，手动出发发布消息），发钉钉，短信，邮件通知运维人工介入处理
        }
        return courseOrder.getOrderNo();
    }
}
